#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>   //注意这个是新加的

#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define GPIOBEEP_CNT            1           /* 设备号个数 */
#define GPIOBEEP_NAME       "gpiobeep"       /* 名字 */
#define BEEPOFF                0            /* 关灯 */
#define BEEPON                 1            /* 开灯 */

/* gpiobeep设备结构体 */
struct gpiobeep_dev{
    dev_t         devid;    /* 设备号   */
    struct cdev   cdev;     /* cdev     */
    struct class  *class;   /* 类       */
    struct device *device;  /* 设备     */
    int           major;    /* 主设备号 */
    int           minor;    /* 次设备号 */
    struct device_node *nd; /* 设备节点 */
    int           beep_gpio; /* beep使用的GPIO编号*/
};

struct gpiobeep_dev gpiobeep;    /* beep设备 */

/*
 * @description   : 打开设备
 * @param - inode : 传递给驱动的inode
 * @param - filp  : 设备文件，file结构体有个叫做private_data的成员变量
 *                    一般在open的时候将private_data指向设备结构体。
 * @return        : 0 成功;其他 失败
 */
static int gpiobeep_open(struct inode *inode, struct file *filp)
{
    printk("gpiobeep open!\n");
    filp->private_data = &gpiobeep; /* 设置私有数据 */
    return 0;
}


/*
 * @description  : 从设备读取数据 
 * @param - filp : 要打开的设备文件(文件描述符)
 * @param - buf  : 返回给用户空间的数据缓冲区
 * @param - cnt  : 要读取的数据长度
 * @param - offt : 相对于文件首地址的偏移
 * @return       : 读取的字节数，如果为负值，表示读取失败
 */
static ssize_t gpiobeep_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    printk("gpiobeep read!\n");
    return 0;
}

/*
 * @description  : 向设备写数据 
 * @param - filp : 设备文件，表示打开的文件描述符
 * @param - buf  : 要写给设备写入的数据
 * @param - cnt  : 要写入的数据长度
 * @param - offt : 相对于文件首地址的偏移
 * @return       : 写入的字节数，如果为负值，表示写入失败
 */
static ssize_t gpiobeep_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    unsigned char databuf[1];
    unsigned char beepstat;
    
    struct gpiobeep_dev *dev = filp->private_data;
    
    /* 接收用户空间传递给内核的数据并且打印出来 */
    if(0 != copy_from_user(databuf, buf, cnt))
    {
        printk("kernel recevdata faibeep!\n");
        return -EFAULT;
    }
    
    beepstat = databuf[0];        /* 获取状态值 */

    if(beepstat == BEEPON) 
    {    
        gpio_set_value(dev->beep_gpio, 1);         /* 打开BEEP灯 */
        printk("beep on!\n");
    } 
    else if(beepstat == BEEPOFF) 
    {
        gpio_set_value(dev->beep_gpio, 0);        /* 关闭BEEP灯 */
        printk("beep off!\n");
    }
    
    return 0;
}


/*
 * @description   : 关闭/释放设备
 * @param - filp  : 要关闭的设备文件(文件描述符)
 * @return        : 0 成功;其他 失败
 */
static int gpiobeep_release(struct inode *inode, struct file *filp)
{
    printk("gpiobeep release！\n");
    return 0;
}

/* 设备操作函数 */
static struct file_operations gpiobeep_fops = {
    .owner   = THIS_MODULE,    
    .open    = gpiobeep_open,
    .read    = gpiobeep_read,
    .write   = gpiobeep_write,
    .release = gpiobeep_release,
};

/*
 * @description   : BEEP硬件初始化(IO映射、时钟、GPIO配置)
 * @param         : 无
 * @return        : 0 成功;其他 失败
 */
static int gpiobeep_hardware_init(void)
{
    int ret;

    /* 获取设备树中的属性数据 */
    /* 1、获取设备节点：gpiobeep */
    gpiobeep.nd = of_find_node_by_path("/gpiobeep");
    if(gpiobeep.nd == NULL) 
    {
        printk("gpiobeep node not find!\r\n");
        return -EINVAL;
    } 
    else 
    {
        printk("gpiobeep node find!\r\n");
    }

    /* 2、获取gpio属性, 得到BEEP编号 */
    gpiobeep.beep_gpio = of_get_named_gpio(gpiobeep.nd, "beep-gpios", 0);
    if(gpiobeep.beep_gpio < 0) 
    {
        printk("can't get beep-gpios!\r\n");
        return -EINVAL;
    } 
    else 
    {
        printk("beep-gpios num = %d\r\n", gpiobeep.beep_gpio);
    }

    /* 3、设置GPIO为输出, 并默认关闭BEEP */
    ret = gpio_direction_output(gpiobeep.beep_gpio, 0);
    if(ret < 0)
    {
        printk("can't set beep-gpio!\r\n");
    }
    
    return 0;
}

/*
 * @description   : 驱动出口函数
 * @param         : 无
 * @return        : 0 成功;其他 失败
 */
static int __init gpiobeep_init(void)
{
    /* 初始化BEEP */
    int ret = gpiobeep_hardware_init();
    if(0 != ret)
    {
        return ret;
    }

    /* 注册字符设备驱动 */
    /* 1、创建设备号 */
    if (gpiobeep.major) /* 定义了设备号 */
    {
        gpiobeep.devid = MKDEV(gpiobeep.major, 0);
        register_chrdev_region(gpiobeep.devid, GPIOBEEP_CNT, GPIOBEEP_NAME);
    } 
    else /* 没有定义设备号 */
    {
        alloc_chrdev_region(&gpiobeep.devid, 0, GPIOBEEP_CNT, GPIOBEEP_NAME);    /* 申请设备号 */
        gpiobeep.major = MAJOR(gpiobeep.devid);    /* 获取分配号的主设备号 */
        gpiobeep.minor = MINOR(gpiobeep.devid);    /* 获取分配号的次设备号 */
    }
    printk("gpiobeep major=%d,minor=%d\n",gpiobeep.major, gpiobeep.minor);    
    
    /* 2、初始化cdev */
    gpiobeep.cdev.owner = THIS_MODULE;
    cdev_init(&gpiobeep.cdev, &gpiobeep_fops);
    
    /* 3、添加一个cdev */
    cdev_add(&gpiobeep.cdev, gpiobeep.devid, GPIOBEEP_CNT);

    /* 4、创建类 */
    gpiobeep.class = class_create(THIS_MODULE, GPIOBEEP_NAME);
    if (IS_ERR(gpiobeep.class)) 
    {
        return PTR_ERR(gpiobeep.class);
    }

    /* 5、创建设备 */
    gpiobeep.device = device_create(gpiobeep.class, NULL, gpiobeep.devid, NULL, GPIOBEEP_NAME);
    if (IS_ERR(gpiobeep.device)) 
    {
        return PTR_ERR(gpiobeep.device);
    }
    
    printk("gpiobeep init done!\n");
    return 0;
}

/*
 * @description   : 驱动出口函数
 * @param         : 无
 * @return        : 无
 */
static void __exit gpiobeep_exit(void)
{
    /* 注销字符设备驱动 */
    cdev_del(&gpiobeep.cdev);/*  删除cdev */
    unregister_chrdev_region(gpiobeep.devid, GPIOBEEP_CNT); /* 注销设备号 */

    device_destroy(gpiobeep.class, gpiobeep.devid);
    class_destroy(gpiobeep.class);
    
    printk("gpiobeep exit done!\n");
}

/* 
 * 将上面两个函数指定为驱动的入口和出口函数 
 */
module_init(gpiobeep_init);
module_exit(gpiobeep_exit);

/* 
 * LICENSE和作者信息
 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zzy");
